package com.core.gwt.place.client;

import com.google.gwt.activity.shared.Activity;
import com.google.gwt.activity.shared.ActivityMapper;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.RunAsyncCallback;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.place.shared.Place;
import com.google.gwt.place.shared.PlaceController;
import com.google.gwt.place.shared.PlaceHistoryMapper;
import com.google.gwt.user.client.ui.AcceptsOneWidget;

/**
 * Extend this class to introduce a code-splitting point in your application.<br>
 * <br>
 * You can defer the loading of the client code implementing a sub-set of your
 * application's activities and their views until the user jumps to a place
 * that's handled by one of those activities.<br>
 * <br>
 * To do that:
 * <ul>
 * <li>have all places that will be handled by activities in the code-split
 * module extend a common abstract subclass of {@link Place};</li>
 * <li>make those places known to the application's {@link PlaceHistoryMapper};</li>
 * <li>implement {@link CodeSplitModule}, which is really an
 * {@link ActivityMapper} that gets loaded asynchronously;</li>
 * <li>create a proxy activity class for your code-split module by extending
 * this class and implement the {@link #createModule} method to return a new
 * instance of your {@link CodeSplitModule} implementation;</li>
 * <li>in the parent application module's {@link ActivityMapper} implementation,
 * return a new instance of that proxy activity whenever the place is an
 * instance of the code-split module's common place superclass:</li>
 * </ul>
 * 
 * <pre>
 * public class MainActivityMapper implements ActivityMapper {
 *    {@literal @Override}
 *     public Activity getActivity(final Place place) {
 *         ...
 *         else if (place instanceof MyModulePlace) {
 *             activity = new MyModuleProxyActivity(...);
 *         }
 *         ...
 *     }
 * }
 * </pre>
 * 
 * The code-split module appears to the parent module as a single activity - the
 * proxy activity extending this class. When that activity gets started for the
 * first time, it asynchronously creates the {@link CodeSplitModule} instance,
 * calls its load() method and from that point on uses it to lookup and start
 * the actual activity to handle the current place. The proxy forwards all
 * {@link Activity} method calls to that actual activity.
 * 
 * @see GWT#runAsync(RunAsyncCallback)
 * @see CodeSplitModule

 */
public abstract class CodeSplitProxyActivity implements Activity {
	private static CodeSplitModule module;

	private final Place place;
	private final PlaceController placeController;
	private final PlaceHistoryMapper placeHistoryMapper;
	private boolean cancelled;
	private Activity activity;

	/**
	 * Initializes a new instance of the code-split proxy activity.
	 * 
	 * @param place
	 *            the {@link Place}
	 * @param placeController
	 *            the application's {@link PlaceController}
	 * @param placeHistoryMapper
	 *            the application's {@link PlaceHistoryMapper}
	 */
	public CodeSplitProxyActivity(final Place place,
			final PlaceController placeController,
			final PlaceHistoryMapper placeHistoryMapper) {
		this.place = place;
		this.placeController = placeController;
		this.placeHistoryMapper = placeHistoryMapper;
	}

	/**
	 * Override to return a new instance of an object implementing
	 * {@link CodeSplitModule}.
	 */
	protected abstract CodeSplitModule createModule();

	@Override
	public String mayStop() {
		String result = null;
		if (activity != null) {
			result = activity.mayStop();
		}
		return result;
	}

	@Override
	public void onCancel() {
		if (activity != null) {
			activity.onCancel();
		}
		cancelled = true;
	}

	/**
	 * Called if the asynchronous loading of the split code fails. Override to
	 * handle the error. The base implementation logs a message to the
	 * development shell logger in hosted mode and nothing in web mode.
	 */
	protected void onLoadFailure(final AcceptsOneWidget panel,
			final EventBus eventBus, final Throwable reason) {
		GWT.log("RunAsyncCallback.onFailure()", reason);
	}

	@Override
	public void onStop() {
		if (activity != null) {
			activity.onStop();
		}
	}

	@Override
	public void start(final AcceptsOneWidget panel, final EventBus eventBus) {
		cancelled = false;
		GWT.runAsync(new RunAsyncCallback() {
			@Override
			public void onFailure(final Throwable reason) {
				onLoadFailure(panel, eventBus, reason);
			}

			@Override
			public void onSuccess() {
				if (!cancelled) {
					if (module == null) {
						module = createModule();
						if (module != null) {
							module.onLoad(placeController, placeHistoryMapper);
						}
					}
					if (module != null) {
						activity = module.getActivity(place);
					}
					if (activity != null) {
						activity.start(panel, eventBus);
					}
				}
			}
		});
	}
}
